package containers;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;
import objects.FixedMass;
import objects.Mass;
import objects.Spring;
import objects.WallRepulsion;


/**
 * Simulates objects moving around in a bounded environment.
 * 
 * @author Robert C. Duvall edited by rwb10 & sab60
 */
public class Simulation {
    /**
     * The spring constant for springs created by mouse interaction.
     */
    private static final double MOUSE_SPRING_CONSTANT = .05;
    private Canvas myContainer;
    private ArrayList<Assembly> myAssemblies;
    private ArrayList<WallRepulsion> myWallRepulsions;
    // elements temporarily created on mouse click
    private Mass myMouseMass;
    private Spring myMouseSpring;

    /**
     * Create a Canvas with the given size.
     * 
     * @param container Canvas to paint on.
     */
    public Simulation (Canvas container) {
        myContainer = container;
        myAssemblies = new ArrayList<Assembly>();
        myWallRepulsions = new ArrayList<WallRepulsion>();
        // LOAD WallRepulsion DEFAULTS:
        addWallRepulsion(new WallRepulsion(1, 0.005, 2.0));
        addWallRepulsion(new WallRepulsion(2, 0.005, 2.0));
        addWallRepulsion(new WallRepulsion(3, 0.005, 2.0));
        addWallRepulsion(new WallRepulsion(4, 0.005, 2.0));
    }

    /**
     * Loads environment into the simulation. Asks for user input
     * to do so, but only loads environment.xsp if approved.
     */
    public void loadEnvironment () {
        try {
            Scanner input = new Scanner(new File("src/data/environment.xsp"));
            while (input.hasNext()) {
                Scanner line = new Scanner(input.nextLine());
                if (line.hasNext()) {
                    String type = line.next();
                    if ("gravity".equals(type)) {
                        loadGravity(line);
                    }
                    else if ("viscosity".equals(type)) {
                        loadViscosity(line);
                    }
                    else if ("centermass".equals(type)) {
                        loadCenter(line);
                    }
                    else if ("wall".equals(type)) {
                        loadWall(line);
                    }
                }
            }
            input.close();
        }
        catch (FileNotFoundException fe) {
            System.out.println("Something went wrong finding the environment.\n" +
                               "It either could not be found or is missnamed.\n" +
                               "Not a problem because its optional. Loading defaults.");

        }
    }

    private void loadWall (Scanner line) {
        addWallRepulsion(new WallRepulsion(line.nextInt(), line.nextDouble(), line.nextDouble()));
    }

    private void loadCenter (Scanner line) {
        Mass.setCenterOfMass(line.nextDouble(), line.nextDouble());
    }

    private void loadGravity (Scanner line) {
        Mass.setGravity(line.nextDouble(), line.nextDouble());
    }

    private void loadViscosity (Scanner line) {
        Mass.setViscosity(line.nextDouble());
    }

    /**
     * Updates assemblies.
     * 
     * @param dt amount of time passed since last update.
     */
    public void update (double dt) {
        for (Assembly a : myAssemblies) {
            a.update(dt);
        }
    }

    /**
     * Paint all the assemblies.
     * 
     * @param pen Pen to draw with.
     */
    public void paint (Graphics2D pen) {
        for (Assembly a : myAssemblies) {
            a.paint(pen);
        }
    }

    /**
     * Add an assembly to simulation.
     * 
     * @param assembly Assembly to add.
     */
    public void addAssembly (Assembly assembly) {
        myAssemblies.add(assembly);
    }

    /**
     * Add a wall repulsion to simulation.
     * 
     * @param wr wall repulsion to add.
     */
    public void addWallRepulsion (WallRepulsion wr) {
        for (WallRepulsion w : myWallRepulsions) {
            if (w.match(wr.getID())) {
                myWallRepulsions.remove(w);
                myWallRepulsions.add(wr);
                return;
            }
        }
        myWallRepulsions.add(wr);
    }

    /**
     * Get the wall repulsions.
     * 
     * @return Returns the wall repulsions arraylist.
     */
    public ArrayList<WallRepulsion> getWallRepulsions () {
        return myWallRepulsions;
    }

    /**
     * Clear the simulation of all objects.
     */
    public void clearAssemblies () {
        myAssemblies.clear();
    }

    /**
     * Get all of the masses in the sim from all assemblies.
     * 
     * @return all the masses in the simulation.
     */
    public ArrayList<Mass> getMasses () {
        ArrayList<Mass> allMasses = new ArrayList<Mass>();
        for (Assembly a : myAssemblies) {
            allMasses.addAll(a.getMasses());
        }
        return allMasses;
    }

    /**
     * Returns size (in pixels) of the game area.
     */
    public Dimension getSize () {
        return myContainer.getSize();
    }

    /**
     * Called by the Canvas when the mouse is pressed down.
     * Creates a fixed-mass at the location of the mouse with a
     * spring attached to the closest mass.
     * 
     * @param mouseLocation Location of the mouse.
     */
    public void mouseDown (Point mouseLocation) {
        // System.out.println("Mouse Down!");
        Mass closestMass = null;
        double closestDist = Double.MAX_VALUE;
        for (Mass m : getMasses()) {
            if (m.getCenter().distance(mouseLocation) < closestDist) {
                closestDist = m.getCenter().distance(mouseLocation);
                closestMass = m;
            }
        }
        // If there was no closest mass, don't do anything.
        if (closestMass != null) {

            Assembly a = closestMass.getAssembly();
            myMouseMass = new FixedMass(-1, mouseLocation.x, mouseLocation.y,
                    -1);
            a.add(myMouseMass);
            myMouseSpring = new Spring(closestMass, myMouseMass, closestDist,
                    MOUSE_SPRING_CONSTANT);
            a.add(myMouseSpring);

        }
    }

    /**
     * 
     * @param myLastMousePosition Point of last mouse position.
     */
    public void mouseMovement (Point myLastMousePosition) {
        // System.out.println("Dragged!");
        myMouseMass.setCenter(myLastMousePosition.x, myLastMousePosition.y);
    }

    /**
     * Removes the mouse spring and FixedMass when mouse depressed.
     */
    public void mouseUp () {
        // System.out.println("Mouse Up!");
        Assembly a = myMouseMass.getAssembly();
        a.removeSpring(myMouseSpring);
        a.removeMass(myMouseMass);
    }

    /**
     * Finds the wall repulsion for a certain wall ID.
     * 
     * @param id Wall ID being searched for.
     * @return The WallRepulsion or null.
     */
    public WallRepulsion findWallRepulsion (int id) {
        for (WallRepulsion wr : myWallRepulsions) {
            if (wr.match(id)) {
                return wr;
            }
        }
        return null;
    }

    /**
     * Returns the canvas container.
     * 
     * @return Canvas container.
     */
    public Canvas getContainer () {
        return myContainer;
    }
}
